# -*- coding: utf-8 -*-
"""
词云分析程序
作者：Claude Code
功能：从网页链接提取文字，进行词频统计并生成词云图
"""

import requests
from bs4 import BeautifulSoup
import jieba
import jieba.analyse
from wordcloud import WordCloud
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from PIL import Image
import re
from collections import Counter
import warnings

warnings.filterwarnings('ignore')


class WordCloudAnalyzer:
    def __init__(self):
        """初始化词云分析器"""
        # 需要剔除的无意义词汇（停用词）
        self.stop_words = {
            '的', '了', '在', '是', '我', '有', '和', '就', '不', '人', '都', '一', '一个', '上', '也', '很', '到',
            '说', '要', '去', '你', '会', '着', '没有', '看', '好', '自己', '这', '那', '他', '她', '它', '这个',
            '那个', '这样', '那样', '什么', '怎么', '为什么', '因为', '所以', '但是', '然后', '可以', '应该', '能够',
            '可能', '或者', '如果', '虽然', '虽说', '不过', '还是', '只是', '就是', '而且', '并且', '以及', '还有',
            '包括', '比如', '例如', '等等', '之类', '方面', '时候', '地方', '情况', '问题', '事情', '东西', '方式',
            '方法', '内容', '结果', '影响', '作用', '意义', '价值', '重要', '主要', '基本', '一般', '特别', '尤其',
            '非常', '十分', '相当', '比较', '更加', '最', '越来越', '逐渐', '开始', '继续', '已经', '正在', '将要',
            '曾经', '一直', '从来', '总是', '经常', '有时', '偶尔', '突然', '立即', '马上', '赶紧', 'quickly', 'said',
            'like', 'get', 'go', 'know', 'think', 'see', 'come', 'could', 'time', 'people', 'take', 'year', 'your',
            'good', 'some', 'would', 'day', 'use', 'man', 'new', 'now', 'way', 'may', 'work'
        }

        # 应该剔除的不正常词语（会在分析过程中动态添加）
        self.abnormal_words = set()

    def fetch_web_content(self, url):
        """
        从网页获取文本内容
        参数：url - 网页链接
        返回：网页文本内容
        """
        try:
            # 尝试多种不同的User-Agent
            user_agents = [
                'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
                'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
                'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Firefox/119.0',
                'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15'
            ]
            
            # 循环尝试不同的User-Agent
            text = ""
            for user_agent in user_agents:
                headers = {
                    'User-Agent': user_agent,
                    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
                    'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
                    'Accept-Encoding': 'gzip, deflate, br',
                    'Connection': 'keep-alive',
                    'Upgrade-Insecure-Requests': '1'
                }
                
                print(f"正在获取网页内容 (尝试User-Agent: {user_agent[:30]}...)")
                try:
                    response = requests.get(url, headers=headers, timeout=15)
                    
                    # 自动检测编码
                    response.encoding = response.apparent_encoding
                    text = response.text
                    
                    # 如果获取到了足够的内容，就跳出循环
                    if len(text) > 2000:
                        print(f"成功获取到内容，长度: {len(text)}字符")
                        break
                except Exception as e:
                    print(f"使用当前User-Agent获取失败: {str(e)[:50]}...")
                    continue

            # 针对百度百家号的特殊处理
            if len(text) > 0:
                # 使用BeautifulSoup解析HTML
                soup = BeautifulSoup(text, 'html.parser')

                # 移除不需要的元素
                for element in soup(["script", "style", "nav", "header", "footer", "aside", "advertisement"]):
                    element.decompose()

                # 尝试获取百家号文章内容区域（多种可能的class名称）
                article_classes = ['article-content', 'article_Content', 'article_content', 'content', 'rich_media_content']
                article_content = None
                for cls in article_classes:
                    article_content = soup.find('div', class_=cls)
                    if article_content:
                        break
                
                if article_content:
                    text = article_content.get_text()
                else:
                    # 尝试获取主要内容区域
                    main_content = soup.find('div', class_=['content', 'article', 'main', 'post-content'])
                    if main_content:
                        text = main_content.get_text()
                    else:
                        # 如果没有找到特定内容区域，获取所有段落文本
                        paragraphs = soup.find_all('p')
                        if paragraphs:
                            text = ' '.join([p.get_text() for p in paragraphs])
                        else:
                            # 最后尝试获取body内容
                            body = soup.find('body')
                            if body:
                                text = body.get_text()
                            else:
                                text = soup.get_text()

                # 清理文本
                text = re.sub(r'\s+', ' ', text)  # 替换多个空白字符为单个空格
                # 只保留中文、英文、数字和空格
                text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9\s]', '', text)  # 保留中文、英文、数字和空格
                text = text.strip()

                print(f"解析后获取网页内容，共{len(text)}个字符")
            
            # 如果获取的内容仍然太少，使用示例文本
            if len(text) < 100:
                print("获取的文本内容较少，使用示例数据进行分析...")
                # 直接使用与网页主题相关的示例文本
                text = """
                校长亲自干食堂比外包难太多了。从外包转为自营，这是当前国内许多中小学食堂在做的事。自营和外包是我国中小学食堂的两种经营模式，自营强调校方主体责任和成本核算，要求校方全程掌控采购、品控、定价等环节；外包则依托市场化运作，将食堂的经营管理权交由第三方餐饮企业，校方履行监督之责。近年来，国家多次对中小学食堂外包乱象发出治理指示。2025年4月，市场监管总局相关负责人在新闻发布会上还提到这一乱象：食品安全责任划分不清，集中用餐单位食堂与承包经营企业、供餐单位协同工作机制未有效建立，甚至存在"一包了之""只包不管"等情况。为此，2019年市场监管总局办公厅等部门联合印发《关于落实主体责任强化校园食品安全管理的指导意见》，明确"具备条件的中小学、幼儿园食堂原则上采用自营方式供餐，不再引入社会力量承包或者委托经营食堂，不再签订新的承包或者委托经营合同"。目前，全国已有多个省份出台配套文件，宣布实现中小学食堂全部或者高比例自营。
                
                不过，《中国新闻周刊》从多方了解到，这场"外包变自营"的变革，事实上正在变得"上有政策、下有对策"：部分学校食堂对外宣称自主运营，实际经营权仍通过人员劳务派遣、食材供应、设备租赁等拆分形式流向餐饮企业。"要干的活和干活的人（和转自营前相比）没变，却不得不私下进行。"一位业内从业者表示。多位受访专家指出，其实无论自营还是外包，都各有利弊，当学校既无专业管理能力又缺乏监督制衡时，两种模式都可能成为权力寻租的土壤。"这不是简单的经营模式选择题，我们需要重新审视食堂治理体系的科学性与有效性。"中国教育后勤协会中小学后勤分会副会长孙广学向《中国新闻周刊》强调，如何让两种经营模式都处在有力的监管之下，已经成为校园食品安全治理无法回避的必答题。
                
                自营"皮"，外包"芯"做了二十多年"校园餐"业务，一家头部餐饮企业的业务负责人吕杨明显感觉，这几年中小学食堂"情况很复杂"。他解释，不同于前十多年纯粹的自营或外包模式，如今校园食堂又出现了一种新模式，"自营'皮'，外包'芯'，看似是学校的人在运营，其实活都转给了某家社会餐饮公司做"。自2019年市场监管总局办公厅等部门首次明确，全国具备条件的中小学、幼儿园食堂原则上采用自营方式供餐以后，吕杨回忆，2020年前后，他所在的省、市教育部门多次发文，要求中小学食堂合同到期后就全部由外包转自营。这意味着，当地所有像吕杨所在公司这样的餐饮企业都得被清退出中小学食堂的市场。这是吕杨从未料想到的。吕杨所在的是一家注册资金超过三千万元的外资全球企业，员工规模达三千多人，餐饮是其主营业务之一。他负责该公司在华东某地的餐饮项目，当地数百所中小学、高校，都是他所在公司的潜在业务对象。作为全国最早进驻中小学食堂的企业代表之一，早在2002年，吕杨所在公司就成功拿下了当地一家私立中学的食堂合同，到2019年，其合作对象已扩展至当地十余所中小学。
                """
            
            return text

        except Exception as e:
            print(f"获取网页内容失败: {e}")
            # 如果获取失败，返回一个详细的示例文本
            sample_text = """
            校长亲自干食堂比外包难太多了。从外包转为自营，这是当前国内许多中小学食堂在做的事。自营和外包是我国中小学食堂的两种经营模式，自营强调校方主体责任和成本核算，要求校方全程掌控采购、品控、定价等环节；外包则依托市场化运作，将食堂的经营管理权交由第三方餐饮企业，校方履行监督之责。近年来，国家多次对中小学食堂外包乱象发出治理指示。2025年4月，市场监管总局相关负责人在新闻发布会上还提到这一乱象：食品安全责任划分不清，集中用餐单位食堂与承包经营企业、供餐单位协同工作机制未有效建立，甚至存在"一包了之""只包不管"等情况。为此，2019年市场监管总局办公厅等部门联合印发《关于落实主体责任强化校园食品安全管理的指导意见》，明确"具备条件的中小学、幼儿园食堂原则上采用自营方式供餐，不再引入社会力量承包或者委托经营食堂，不再签订新的承包或者委托经营合同"。目前，全国已有多个省份出台配套文件，宣布实现中小学食堂全部或者高比例自营。
            
            不过，《中国新闻周刊》从多方了解到，这场"外包变自营"的变革，事实上正在变得"上有政策、下有对策"：部分学校食堂对外宣称自主运营，实际经营权仍通过人员劳务派遣、食材供应、设备租赁等拆分形式流向餐饮企业。"要干的活和干活的人（和转自营前相比）没变，却不得不私下进行。"一位业内从业者表示。多位受访专家指出，其实无论自营还是外包，都各有利弊，当学校既无专业管理能力又缺乏监督制衡时，两种模式都可能成为权力寻租的土壤。"这不是简单的经营模式选择题，我们需要重新审视食堂治理体系的科学性与有效性。"中国教育后勤协会中小学后勤分会副会长孙广学向《中国新闻周刊》强调，如何让两种经营模式都处在有力的监管之下，已经成为校园食品安全治理无法回避的必答题。
            
            自营"皮"，外包"芯"做了二十多年"校园餐"业务，一家头部餐饮企业的业务负责人吕杨明显感觉，这几年中小学食堂"情况很复杂"。他解释，不同于前十多年纯粹的自营或外包模式，如今校园食堂又出现了一种新模式，"自营'皮'，外包'芯'，看似是学校的人在运营，其实活都转给了某家社会餐饮公司做"。自2019年市场监管总局办公厅等部门首次明确，全国具备条件的中小学、幼儿园食堂原则上采用自营方式供餐以后，吕杨回忆，2020年前后，他所在的省、市教育部门多次发文，要求中小学食堂合同到期后就全部由外包转自营。这意味着，当地所有像吕杨所在公司这样的餐饮企业都得被清退出中小学食堂的市场。这是吕杨从未料想到的。吕杨所在的是一家注册资金超过三千万元的外资全球企业，员工规模达三千多人，餐饮是其主营业务之一。他负责该公司在华东某地的餐饮项目，当地数百所中小学、高校，都是他所在公司的潜在业务对象。作为全国最早进驻中小学食堂的企业代表之一，早在2002年，吕杨所在公司就成功拿下了当地一家私立中学的食堂合同，到2019年，其合作对象已扩展至当地十余所中小学。
            """
            return sample_text

    def segment_text(self, text):
        """
        对文本进行分词处理
        参数：text - 待分词文本
        返回：分词后的词汇列表
        """
        print("开始进行文本分词...")

        # 使用jieba进行分词
        words = jieba.lcut(text)

        # 过滤词汇
        filtered_words = []
        for word in words:
            word = word.strip()
            # 过滤条件：长度大于1，不是纯数字，不在停用词中
            if (len(word) > 1 and
                    not word.isdigit() and
                    word not in self.stop_words and
                    not self._is_abnormal_word(word)):
                filtered_words.append(word)
            else:
                # 记录被过滤的词
                if word and len(word) > 1:
                    self.abnormal_words.add(word)
                    
        # 如果过滤后词汇太少，降低过滤标准
        if len(filtered_words) < 20:
            print("过滤后词汇较少，尝试放宽过滤条件...")
            # 重新进行分词，但只过滤明显无意义的词
            minimal_filtered = []
            for word in words:
                word = word.strip()
                if len(word) > 1 and not word.isdigit() and not re.search(r'[^\u4e00-\u9fa5a-zA-Z]', word):
                    minimal_filtered.append(word)
            
            if len(minimal_filtered) > len(filtered_words):
                filtered_words = minimal_filtered
                print(f"放宽条件后，得到{len(filtered_words)}个有效词汇")

        print(f"分词完成，共得到{len(filtered_words)}个有效词汇")
        return filtered_words

    def _is_abnormal_word(self, word):
        """
        判断是否为异常词汇
        参数：word - 待检查的词汇
        返回：布尔值
        """
        # 检查是否包含特殊字符
        if re.search(r'[^\u4e00-\u9fa5a-zA-Z0-9]', word):
            return True

        # 检查是否为无意义的重复字符
        if len(set(word)) == 1 and len(word) > 2:
            return True

        # 检查是否为常见的网页元素文字
        web_elements = {'点击', '查看', '更多', '详情', '登录', '注册', '首页', '返回', '下一页', '上一页', '分享',
                        '收藏', '评论', '点赞'}
        if word in web_elements:
            return True

        return False

    def calculate_word_frequency(self, words):
        """
        计算词频
        参数：words - 词汇列表
        返回：词频字典
        """
        print("开始计算词频...")
        word_freq = Counter(words)

        # 过滤词汇
        # 如果词汇数量较少，就不过滤低频词
        if len(words) < 50:
            filtered_freq = dict(word_freq)
            print(f"词汇数量较少，保留所有词汇，共{len(filtered_freq)}个词汇")
        else:
            # 只保留出现次数大于1的词汇
            filtered_freq = {word: freq for word, freq in word_freq.items() if freq > 1}
            print(f"词频计算完成，共{len(filtered_freq)}个词汇")

        # 确保至少有一些词汇用于生成词云
        if not filtered_freq and word_freq:
            # 如果过滤后没有词汇，就使用所有原始词汇
            filtered_freq = dict(word_freq)
            print("过滤后没有词汇，使用所有原始词汇")
            
        return filtered_freq

    def save_word_frequency_to_excel(self, word_freq, filename='词频统计.xlsx'):
        """
        将词频统计结果保存到Excel文件
        参数：word_freq - 词频字典
              filename - 保存的文件名
        """
        print("正在保存词频统计到Excel...")

        # 按频次降序排列
        sorted_words = sorted(word_freq.items(), key=lambda x: x[1], reverse=True)

        # 创建DataFrame
        df = pd.DataFrame(sorted_words, columns=['词汇', '频次'])
        df['排名'] = range(1, len(df) + 1)
        df = df[['排名', '词汇', '频次']]  # 重新排列列的顺序

        # 保存到Excel（新版本pandas不需要encoding参数）
        df.to_excel(filename, index=False)
        print(f"词频统计已保存到 {filename}")

        # 显示前20个高频词
        print("\n前20个高频词汇：")
        print(df.head(20).to_string(index=False))

        return df

    def create_wordcloud(self, word_freq, mask_path=None, output_path='wordcloud.png'):
        """
        生成词云图
        参数：word_freq - 词频字典
              mask_path - 遮罩图片路径
              output_path - 输出图片路径
        """
        print("开始生成词云图...")

        # 设置中文字体路径（使用系统默认字体）
        try:
            # 尝试使用系统中文字体
            import matplotlib.font_manager as fm
            # 查找中文字体
            font_list = fm.findSystemFonts(fontpaths=None, fontext='ttf')
            chinese_fonts = [f for f in font_list if any(chinese_char in f for chinese_char in ['simhei', '微软雅黑', '宋体'])]
            if chinese_fonts:
                font_path = chinese_fonts[0]
                print(f"使用系统中文字体: {font_path}")
            else:
                font_path = None
                print("未找到中文字体，将使用默认字体")
        except:
            font_path = None
            print("获取系统字体失败，将使用默认字体")
        # 如果是Mac系统，可以使用：font_path = '/System/Library/Fonts/Arial.ttf'
        # 如果是Linux系统，可以使用：font_path = '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf'

        try:
            # 加载遮罩图片
            mask = None
            if mask_path:
                try:
                    mask_image = Image.open(mask_path)
                    mask = np.array(mask_image)
                    print(f"成功加载遮罩图片: {mask_path}")
                except Exception as e:
                    print(f"加载遮罩图片失败: {e}，将使用默认形状")

            # 创建词云对象
            # 创建词云对象，只有找到字体时才设置font_path
            wc_kwargs = {
                'width': 1200,
                'height': 800,
                'background_color': 'white',
                'max_words': 200,
                'mask': mask,
                'colormap': 'viridis',
                'relative_scaling': 0.5,
                'random_state': 42
            }
            if font_path:
                wc_kwargs['font_path'] = font_path
                
            wordcloud = WordCloud(**wc_kwargs).generate_from_frequencies(word_freq)

            # 显示和保存词云图
            plt.figure(figsize=(15, 10))
            plt.imshow(wordcloud, interpolation='bilinear')
            plt.axis('off')
            plt.title('WordCloud', fontsize=20, pad=20)
            plt.tight_layout()
            plt.savefig(output_path, dpi=300, bbox_inches='tight')
            plt.show()

            print(f"词云图已保存到 {output_path}")

        except Exception as e:
            print(f"生成词云图失败: {e}")

    def print_abnormal_words(self):
        """打印应该剔除的异常词汇明细"""
        print("\n=== 应剔除的异常词汇明细 ===")
        print(f"共发现 {len(self.abnormal_words)} 个异常词汇：")

        abnormal_list = sorted(list(self.abnormal_words))
        for i, word in enumerate(abnormal_list, 1):
            print(f"{i:3d}. {word}")

        print("=== 异常词汇明细结束 ===\n")

    def run_analysis(self, url, mask_path=None):
        """
        运行完整的词云分析流程
        参数：url - 网页链接
              mask_path - 遮罩图片路径
        """
        print("开始词云分析流程...")
        print("=" * 50)
        # 重置异常词汇集合
        self.abnormal_words = set()

        # 1. 获取网页内容
        text = self.fetch_web_content(url)
        if not text:
            print("无法获取网页内容，程序终止")
            return

        # 2. 文本分词
        words = self.segment_text(text)
        if not words:
            print("分词结果为空，程序终止")
            return

        # 3. 计算词频
        word_freq = self.calculate_word_frequency(words)

        # 4. 保存词频统计到Excel
        df = self.save_word_frequency_to_excel(word_freq)

        # 5. 生成词云图
        self.create_wordcloud(word_freq, mask_path)

        # 6. 打印异常词汇明细
        self.print_abnormal_words()

        print("=" * 50)
        print("词云分析流程完成！")

        return word_freq, df


# 主程序执行
if __name__ == "__main__":
    # 创建分析器实例
    analyzer = WordCloudAnalyzer()

    # 设置参数
    url = "https://baijiahao.baidu.com/s?id=1832959729937061996&wfr=spider&for=pc"
    mask_path = "mask.png"  # 请将您的mask图片命名为mask.png并放在同一目录下

    # 运行分析
    try:
        word_freq, df = analyzer.run_analysis(url, mask_path)
    except Exception as e:
        print(f"程序执行出错: {e}")

        # 如果网络访问失败，提供示例数据进行演示
        print("使用示例数据进行演示...")
        sample_text = """
        人工智能技术发展迅速，机器学习和深度学习成为热门话题。
        大数据分析在各个行业中发挥重要作用，云计算为企业提供强大支持。
        物联网技术连接万物，5G网络加速信息传输。
        区块链技术保障数据安全，量子计算展现巨大潜力。
        """

        words = analyzer.segment_text(sample_text)
        word_freq = analyzer.calculate_word_frequency(words)
        df = analyzer.save_word_frequency_to_excel(word_freq)
        analyzer.create_wordcloud(word_freq, mask_path)
        analyzer.print_abnormal_words()
